package com.zhao.retry.demo;

import com.alibaba.fastjson.JSON;
import com.zhao.retry.demo.entity.RetryRecord;
import com.zhao.retry.demo.mapper.RetryRecordMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created on 2020/5/20.
 *
 * @author zhao
 * @email 136689260@qq.com
 */
@Slf4j
@Component
@Aspect
public class RetryAspect {

    @Autowired
    private RetryRecordMapper retryRecordMapper;

    @Pointcut("@within(Retry) || @annotation(Retry)")
    public void retry() {

    }

    @Around("retry()")
    public Object doRetry(ProceedingJoinPoint pjp) throws Throwable {
        Object obj = null;
        try {
            obj = pjp.proceed();
        } catch (Throwable e) {
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();
            Retry retry = AnnotatedElementUtils.findMergedAnnotation(method, Retry.class);
            if (retry == null) {
                retry = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Retry.class);
            }
            if (retry == null) {
                retry = findAnnotationOnTarget(pjp.getTarget(), method, Retry.class);
            }
            if (retry != null) {
                for (int i = 0; i < retry.times(); i++) {
                    try {
                        return pjp.proceed();
                    } catch (Throwable e1) {
//                        log.error("doRetry", e1);
                    }
                }
                this.saveRetryBody(pjp);
            } else {
                throw e;
            }
        }
        return obj;
    }

    private void saveRetryBody(ProceedingJoinPoint pjp) {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] args = pjp.getArgs();
        RetryRecord retryRecord = new RetryRecord();
        retryRecord.setClassName(pjp.getTarget().getClass().getName());
        retryRecord.setMethodName(methodName);
        Map<String, String> paramMap = new LinkedHashMap<>();
        for (int i = 0; i < parameterTypes.length; i++) {
            paramMap.put(parameterTypes[i].getName(),JSON.toJSONString(args[i]));
        }
        retryRecord.setParameterTypes(JSON.toJSONString(parameterTypes));
        retryRecord.setArgs(JSON.toJSONString(args));
        retryRecord.setParam(JSON.toJSONString(paramMap));
        retryRecord.setStatus(0);
        retryRecordMapper.insert(retryRecord);
    }

    private <A extends Annotation> A findAnnotationOnTarget(Object target, Method method, Class<A> annotation) {
        try {
            Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
            A a = AnnotatedElementUtils.findMergedAnnotation(targetMethod, annotation);
            if (a == null) {
                a = AnnotatedElementUtils.findMergedAnnotation(targetMethod.getDeclaringClass(), annotation);
            }
            return a;
        } catch (Exception e) {
            return null;
        }
    }
}
